<!--
 * @Author: wusd
 * @Date: 2022-05-18 14:58:44
 * @LastEditors: wusd
 * @LastEditTime: 2024-10-31 11:06:01
 * @Description: 
 * 
-->
<script lang="tsx">
import { Component, Vue, Prop, Watch } from 'vue-property-decorator'
import { WTableConfig, WTableColumn, SearchParams, Pagination, WBtn, searchItem } from 'types/table'
import { AnyObj, ApiCode } from 'types/common'
import { CreateElement, VNode } from 'vue'
import { fixShowValue, mergeObject, resetObject, valueIsNull } from '@/untils/index'
import { renderFormElement, renderStaticElement } from '@/untils/renderFunction'

@Component({
  name: 'WTable'
})
export default class WTable extends Vue {
  @Prop({
    default: () => ({
      columns: []
    }),
    type: Object
  })
  tableConfig!: WTableConfig

  @Watch('tableConfig', { deep: true, immediate: true })
  changeConfig() {
    this.init()
  }
  public pageListKey = 'content'
  public isFirst = true
  /** 初始化页面 */
  public init() {
    this.setSearchBoxId()
    this.initTableConfig()
    this.$nextTick(() => {
      this.initSearchDivStyle()
    })
    this.isFirst = false
  }

  public resetIsFirst() {
    this.isFirst = true
  }
  public openBtnShow = true

  /**初始化参数 */
  public async initTableConfig() {
    /**合并配置项 */
    mergeObject(this.tableConfig, Vue.prototype?.$baseConfig?.tableConfig)
    if (this.tableConfig?.fixColumns?.show) {
      this.initColumnWidth()
    }
    this.tableConfig?.search?.list?.forEach(async item => {
      if (item.apiList) {
        let list = await item.apiList()
        item.list = list
        this.$forceUpdate()
      }
    })
    this.httpCodekey = Vue.prototype?.$baseConfig?.httpCodekey || this.httpCodekey
    this.pageListKey = Vue.prototype?.$baseConfig?.pageListKey || this.pageListKey
    if (valueIsNull(this.tableConfig.loading)) {
      this.tableConfig.loading = true
    }
    /** 初始化rowKey，表格主键 */
    this.rowKey = this.tableConfig.nodeData?.props?.['row-key'] || 'id'
    /** 初始化apiCode，后台接口返回成功时的code码 默认0为成功 */
    this.apiCode = this.tableConfig?.apiCode || 0
    if (this.isFirst) {
      /** 初始化searchParams */
      this.initSearch()
      this.getList()
    }
    if (this.tableConfig?.search?.stow === false) {
      this.searchStow = false
    }
    /** 初始化pagination */
    mergeObject(this.tableConfig?.pagination, this.pagination)
  }
  initColumnWidth() {
    let textWidth = this.tableConfig?.fixColumns?.textWidth || 15
    let paddingWidth = this.tableConfig?.fixColumns?.paddingWidth || 20
    let minTextNum = this.tableConfig?.fixColumns?.minTextNum || 4
    let columnNum = this.tableConfig?.fixColumns?.columnNum || 1

    this.flowCol(this.tableConfig.columns, textWidth, paddingWidth, minTextNum, columnNum)
  }
  flowCol(arr: any, textWidth: number, paddingWidth: number, minTextNum: number, columnNum: number) {
    arr.forEach((item: any) => {
      if (item.innerColumnList?.length) {
        this.flowCol(item.innerColumnList, textWidth, paddingWidth, minTextNum, columnNum)
      } else {
        if (!item.width) {
          if (item.type === 'selection') {
            item.width = '50px'
          } else {
            let num = columnNum
            if (item?.label?.length <= minTextNum) {
              num = 1
            }
            item.width = Math.ceil((item?.label?.length || 0) / num) * textWidth + paddingWidth + 'px'
          }
        }
      }
    })
  }
  /** 初始化searchParams */
  initSearch() {
    let searchParams: AnyObj = {
      pageSize: this.tableConfig?.pagination?.pageSize || this.searchParams.pageSize,
      pageNum: this.tableConfig?.pagination?.pageNum || this.searchParams.pageNum
    }
    this.tableConfig?.search?.list?.forEach(item => {
      searchParams[item.prop] = valueIsNull(item.defaultValue) ? '' : item.defaultValue
    })
    this.searchParams = searchParams
    this.originalSearchParams = JSON.parse(JSON.stringify(searchParams))
  }
  /** 初始化搜索盒子样式 */
  initSearchDivStyle() {
    let searchBoxDom: any = document.getElementById(this.searchBoxId)
    if (!searchBoxDom) return
    let domMaxHeight = 0, //第一行元素最大高度
      bool = 0
    searchBoxDom.style['flex-wrap'] = 'wrap'
    let searchItemList = searchBoxDom.getElementsByClassName('w_search_item')
    let wSearchMaxHeight = 0
    for (let i = 0, j = searchItemList.length; i < j; i++) {
      let dom: any = searchItemList[i]
      if (bool < 1) {
        domMaxHeight = domMaxHeight > dom.offsetHeight ? dom.offsetHeight : dom.offsetHeight + 8
      }
      if (!dom.offsetLeft) {
        wSearchMaxHeight += dom.offsetHeight
        wSearchMaxHeight += 8
        bool++
      }
    }
    let minheight = this.tableConfig.search?.minHeight || domMaxHeight
    searchBoxDom.style.height = `${minheight}px`
    searchBoxDom.style.setProperty('--wSearchMaxHeight', `${this.tableConfig.search?.maxHeight || wSearchMaxHeight}px`)
    if (bool === 1 && valueIsNull(this.tableConfig.openBtnShow)) {
      this.openBtnShow = false
    }
  }
  public async getList() {
    this.loading = true
    this.tableConfig?.beforeGetList?.(this)
    this.tableConfig?.api
      ?.list?.({ ...this.searchParams })
      .then((res: AnyObj) => {
        this.loading = false
        this.tableData = res[this.pageListKey]
        this.total = res.total
        this.tableConfig?.afterGetList?.(this)
      })
      .catch(() => {
        this.loading = false
        this.tableData = []
      })
  }
  /** table表格数据 */
  private tableData: AnyObj[] = []
  private rowKey = 'id'
  private apiCode: ApiCode = 0
  public httpCodekey = 'code'

  /** table表格数据总数 */
  private total = 0
  /** 搜索条件默认 */
  public searchParams: SearchParams = {
    pageSize: 10,
    pageNum: 1
  }
  /** 搜索条件默认-defaultValue */
  public originalSearchParams: SearchParams = {
    pageSize: 10,
    pageNum: 1
  }

  /** 是否收起所有搜索条件 */
  public searchStow = true
  /** pagination默认配置 */
  public pagination: Pagination = {
    show: true,
    align: 'right',
    pageSizes: [10, 20, 50, 100],
    layout: 'total, sizes, prev, pager, next, jumper'
  }
  /**
   * 渲染表单元素(搜索项|form表单项)
   */
  private renderSearchlement(
    h: CreateElement,
    params: {
      data: searchItem
    }
  ) {
    return renderFormElement(h, {
      config: params.data,
      model: this.searchParams,
      valueClass: 'w_search_item_value',
      parentConfig: {
        size: this.tableConfig?.search?.size
      }
    })
  }
  /**
   * 渲染操作列
   * @param h
   * @param params
   * @returns
   */
  private renderInnerColumn(
    h: CreateElement,
    params: {
      column: WTableColumn<AnyObj>
    }
  ) {
    let { column } = params
    column.type = 'operation'
    let columnWith = (this.tableConfig?.innerBtns?.length || 0) * (56 + 10) + 14 + 'px'
    return (
      <el-table-column
        align={this.tableConfig.align}
        {...column?.nodeData}
        label={column.label || '操作'}
        width={column.width || columnWith}
        scopedSlots={{
          default: (scope: { row: any; $index: any }) =>
            this.renderColumnText(h, { column: column, row: scope.row, index: scope.$index }),
          ...column?.nodeData?.scopedSlots
        }}></el-table-column>
    )
  }
  /**
   * 渲染每一列
   * @param h
   * @param params
   * @returns
   */
  private renderColumn(
    h: CreateElement,
    params: {
      column: WTableColumn<AnyObj>
    }
  ) {
    let { column } = params
    let dom: any = null
    /**合并表头 */
    if (column?.innerColumnList?.length) {
      dom = (
        <el-table-column
          align={this.tableConfig.align || 'center'}
          label={column.label}
          width={column.width || '50px'}
          prop={column.prop}
          {...column?.nodeData}>
          {column?.innerColumnList?.map(item => {
            return this.renderColumn(h, {
              column: item
            })
          })}
        </el-table-column>
      )
      return dom
    }
    switch (column.type) {
      case 'index':
        dom = (
          <el-table-column
            align={this.tableConfig.align || 'center'}
            type="index"
            label={column.label}
            width={column.width || '50px'}
            {...column?.nodeData}></el-table-column>
        )
        break
      case 'selection':
        dom = (
          <el-table-column
            align={this.tableConfig.align || 'center'}
            type="selection"
            width={column.width || '50px'}
            {...column?.nodeData}></el-table-column>
        )
        break
      default:
        dom = (
          <el-table-column
            align={this.tableConfig.align}
            label={column.label}
            show-overflow-tooltip={this.tableConfig?.tooltip}
            width={column.width}
            prop={column.prop}
            {...column?.nodeData}
            scopedSlots={{
              default: (scope: { row: any; $index: any }) =>
                this.renderColumnText(h, { column: column, row: scope.row, index: scope.$index }),
              ...column?.nodeData?.scopedSlots
            }}></el-table-column>
        )
    }
    return dom
  }
  /** 渲染按钮 */
  private renderBtn(
    h: CreateElement,
    btn: WBtn,
    params: {
      column?: WTableColumn<AnyObj>
      row?: AnyObj
      index?: number
    } = {}
  ) {
    let { row } = params
    let disabled = this.fixDisabled(row, btn.disabled)
    let dom: VNode

    if (btn.render) {
      dom = btn.render(h, row)
    } else {
      dom = (
        <el-button
          class={`w_button_${btn.key}`}
          disabled={disabled}
          {...btn?.nodeData}
          size={btn.size}
          icon={btn.icon}
          onclick={() => this.handleBtn(btn, params)}
          type={btn.type}>
          {btn.text}
        </el-button>
      )
    }
    return dom
  }
  /** 点击按钮 */
  private handleBtn(
    btn: WBtn,
    params: {
      column?: WTableColumn<AnyObj>
      row?: AnyObj
      index?: number
    }
  ) {
    let { row } = params
    /** 如果存在回调函数，则只调用回调 */
    if (btn?.callback) {
      btn.callback(params)
      return
    }
    /** 根据按钮key值调用默认方法 */
    switch (btn.key) {
      case 'delete':
        this.$confirm(btn.submitInfo || '此操作将永久删除该数据, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        })
          .then(() => {
            this.tableConfig?.api?.delete?.(row)?.then((res: AnyObj) => {
              if (res[this.httpCodekey] === Number(this.apiCode)) {
                this.$message({
                  type: 'success',
                  message: '删除成功!'
                })
                /** 处理删除的极值问题 */
                let dataIndex =
                  (this.searchParams?.pageSize || 10) * ((this.searchParams.pageNum || 1) - 1) +
                  (params?.index || 0) +
                  1
                if (dataIndex === this.total) {
                  this.searchParams.pageNum = (this.searchParams?.pageNum || 1) - 1 || 1
                }
                this.getList()
              }
            })
          })
          .catch(() => {
            this.$message({
              type: 'info',
              message: '已取消删除'
            })
          })
        break
    }
  }
  /**
   * 渲染表格内容
   * @param h
   * @param params
   * @returns
   */
  private renderColumnText(
    h: CreateElement,
    params: {
      column: WTableColumn<AnyObj>
      row: AnyObj
      index?: number
    }
  ) {
    let dom: any = null
    let { column, row } = params
    if (column.render) {
      dom = column?.render(h, { ...params })
    } else if (column.componentName) {
      dom = renderStaticElement(h, {
        componentName: column.componentName,
        data: column.prop && row[column.prop],
        ...params,
        _this: this
      })
    } else {
      switch (column.type) {
        case 'image':
          break
        case 'operation':
          dom = (
            <div class={`w_table_operation`}>
              {this.tableConfig.innerBtns
                ?.filter(item => {
                  return fixShowValue(row, item.show)
                })
                ?.map(item => {
                  item.size = item.size || 'small'
                  return this.renderBtn(h, item, params)
                })}
            </div>
          )
          break
        default:
          dom = <span>{column.prop && row[column.prop]}</span>
      }
    }
    return dom
  }
  private handleSizeChange(val: number | undefined) {
    this.searchParams.pageSize = val
    this.searchParams.pageNum = 1
    this.getList()
  }
  private handleNumChange(val: number | undefined) {
    this.searchParams.pageNum = val
    this.getList()
  }
  private async handleSearch(type = 'search') {
    this.searchParams.pageNum = 1
    if (type === 'reset') {
      if (this.tableConfig?.resetFunc) {
        this.tableConfig.resetFunc?.(this)
      } else {
        if (this.tableConfig?.resetIsDefaultValue) {
          this.searchParams = JSON.parse(JSON.stringify(this.originalSearchParams))
        } else {
          this.searchParams = resetObject(this.searchParams)
        }
      }
    }
    if (this.tableConfig?.search?.searchBtn?.callback) {
      this.loading = true
      let res: AnyObj = await this.tableConfig?.search?.searchBtn?.callback(this.searchParams)
      this.tableData = res.content
      this.total = res.total
      this.loading = false
    } else {
      this.getList()
    }
  }

  loading = false

  private fixDisabled(row: any, disabled: any) {
    if (typeof disabled === 'boolean') {
      return disabled
    }
    if (!disabled) {
      return false
    } else {
      return disabled(row)
    }
  }
  searchBoxId = ''
  public setSearchBoxId() {
    this.searchBoxId = `w_search_box_${Math.random()}`
  }

  protected render(h: CreateElement) {
    return (
      <div class="w_table">
        {this.tableConfig?.search?.list?.length && (
          <div id={this.searchBoxId} class={['w_search', this.searchStow ? 'w_search_hidden' : 'w_search_show']}>
            {this.tableConfig?.search?.list?.map(item => {
              return (
                fixShowValue({}, item.show) && (
                  <div
                    class={['w_search_item', 'search_item_' + item.prop]}
                    style={{
                      width: item.span
                        ? (100 * item.span) / 24 + '%'
                        : 100 / (this.tableConfig?.search?.rowNum || 4) + '%'
                    }}
                    {...item?.nodeData}>
                    <span
                      class="w_search_item_label"
                      style={{ width: item?.labelWidth || this.tableConfig?.search?.labelWidth || '80px' }}>
                      {item.label}
                    </span>
                    {this.renderSearchlement(h, { data: item })}
                  </div>
                )
              )
            })}
          </div>
        )}
        <div class="w_outBox">
          <div class="w_outBtns">
            {this.tableConfig.outBtns
              ?.filter(item => {
                return fixShowValue({}, item.show)
              })
              ?.map(item => {
                item.size = item.size || 'small'
                return this.renderBtn(h, item)
              })}
            {this.tableConfig.outBtnsRight
              ?.filter(item => {
                return fixShowValue({}, item.show)
              })
              ?.map(item => {
                item.size = item.size || 'small'
                return this.renderBtn(h, item)
              })}
          </div>
          {this.tableConfig?.search?.list?.length && (
            <div class="w_searchBtns">
              {this.tableConfig.rightBtns
                ?.filter(item => {
                  return fixShowValue({}, item.show)
                })
                ?.map(item => {
                  item.size = item.size || 'small'
                  return this.renderBtn(h, item)
                })}
              {this.tableConfig?.search?.searchBtn?.show !== false && (
                <el-button
                  type="primary"
                  size="small"
                  {...this.tableConfig?.search?.searchBtn?.nodeData}
                  onclick={this.handleSearch}>
                  {this.tableConfig?.search?.searchBtn?.text || '查询'}
                </el-button>
              )}
              {this.tableConfig?.resetBtnShow && (
                <el-button type="primary" size="small" onclick={() => this.handleSearch('reset')}>
                  重置
                </el-button>
              )}
              {this.tableConfig?.openBtnShow !== false && this.openBtnShow && (
                <el-button
                  type="primary"
                  class="w_table_openbtn"
                  size="small"
                  onclick={() => (this.searchStow = !this.searchStow)}>
                  {this.searchStow ? '展开' : '收起'}查询条件
                  <i class={this.searchStow ? 'el-icon-arrow-down' : 'el-icon-arrow-up'}></i>
                </el-button>
              )}
            </div>
          )}
        </div>

        <el-table
          v-loading={this.loading && this.tableConfig?.loading}
          row-key={this.rowKey}
          {...this.tableConfig?.nodeData}
          data={this.tableData}>
          {this.tableConfig.columns
            ?.filter(item => {
              return fixShowValue({}, item.show)
            })
            ?.map(item => {
              return this.renderColumn(h, { column: item })
            })}
          {this.tableConfig.innerBtns?.length &&
            this.renderInnerColumn(h, { column: this.tableConfig.innerColumn || {} })}
        </el-table>
        {this.tableConfig?.pagination?.show && (
          <el-pagination
            style={{ textAlign: this.tableConfig?.pagination.align }}
            class="w_pagination"
            {...this.tableConfig?.pagination?.nodeData}
            onSize-change={this.handleSizeChange}
            onCurrent-change={this.handleNumChange}
            current-page={this.searchParams.pageNum}
            page-sizes={this.tableConfig?.pagination.pageSizes}
            page-size={this.searchParams.pageSize}
            layout={this.tableConfig?.pagination.layout}
            total={this.total}></el-pagination>
        )}
      </div>
    )
  }
}
</script>

<style lang="scss" scoped>
//--wSearchHeight为变量,必须以--开头，否则其它页面使用这个变量不会生效
$wSearchMaxHeight: var(--wSearchMaxHeight, 0px);

.w_table {
  .w_search {
    width: 100%;
    display: flex;
    overflow: hidden;
    transition: all 0.5s;
    position: relative;
    .w_search_item {
      display: flex;
      flex-shrink: 0;
      align-items: center;
      margin-bottom: 8px;
      overflow: hidden;
      .w_search_item_label {
        font-size: 14px;
        margin-right: 8px;
        flex-shrink: 0;
        letter-spacing: 4px;
        text-align: center;
        color: #333;
      }
      .w_search_item_value {
        flex: 1;
      }
    }
  }
  .w_search_show {
    height: $wSearchMaxHeight !important;
  }
  .w_outBox {
    width: 100%;
    display: flex;
    margin-bottom: 16px;
    .w_searchBtns {
      display: flex;
      flex-grow: 1;
      justify-content: flex-end;
    }
  }
  .w_pagination {
    margin-top: 16px;
  }
}
</style>
